feat(landing): add Hyperliquid-first public page#250
Conversation
Reviewer's GuideImplements a new Hyperliquid-first public landing page under /landing with dedicated HTML/CSS/JS, and updates the desktop UI and documentation to align execution strategy, runtime surfaces, and internal messaging around Hyperliquid as the active venue while keeping Kraken as legacy compatibility. Sequence diagram for desktop snapshot refresh and Hyperliquid frontier dashboardsequenceDiagram
participant UI as DesktopUI
participant Timer as RefreshTimer
participant Renderer as RendererModule
participant Bridge as QuantlabDesktopBridge
participant API as ResearchServerAPI
%% Initial workspace state established elsewhere
UI->>Renderer: ensureRefreshLoop()
alt workspace serverUrl missing or refreshPaused
Renderer-->>UI: return (no refresh)
else ready
Renderer->>Renderer: refreshSnapshot()
Renderer->>Timer: setInterval(refreshSnapshot, refreshIntervalMs)
end
loop every refreshIntervalMs
Timer->>Renderer: refreshSnapshot()
par requestSnapshot
Renderer->>Bridge: requestJson(runtimeSnapshotPath)
Bridge->>API: GET /api/runtime-snapshot
API-->>Bridge: runtimeSnapshot
Bridge-->>Renderer: runtimeSnapshot
and requestLaunchControl
Renderer->>Bridge: requestJson(launchControlPath)
Bridge->>API: GET /api/launch-control
API-->>Bridge: launchControl
Bridge-->>Renderer: launchControl
and requestPaperHealth
Renderer->>Bridge: requestJson(paperHealthPath)
Bridge->>API: GET /api/paper-sessions-health
API-->>Bridge: paperHealth
Bridge-->>Renderer: paperHealth
and requestBrokerHealth
Renderer->>Bridge: requestJson(brokerHealthPath)
Bridge->>API: GET /api/broker-submissions-health
API-->>Bridge: brokerHealth
Bridge-->>Renderer: brokerHealth
and requestHyperliquidSurface
Renderer->>Bridge: requestJson(hyperliquidSurfacePath)
Bridge->>API: GET /api/hyperliquid-surface
API-->>Bridge: hyperliquidSurface
Bridge-->>Renderer: hyperliquidSurface
and requestStepbitWorkspace
Renderer->>Bridge: requestJson(stepbitWorkspacePath)
Bridge->>API: GET /api/stepbit-workspace
API-->>Bridge: stepbitWorkspace
Bridge-->>Renderer: stepbitWorkspace
end
Renderer->>Renderer: update state.snapshot with all results
Renderer->>Renderer: update state.snapshotStatus
Renderer->>Renderer: renderWorkspaceState()
Renderer->>Renderer: frontier = getFrontierSnapshot()
Renderer->>Renderer: renderFrontierDashboard(frontier)
Renderer->>UI: update frontierMeta, frontierSummary, frontierGrid, frontierCallout
end
Sequence diagram for research UI process startup retry logicsequenceDiagram
participant Main as DesktopMain
participant Launcher as launchResearchUiProcess
participant Binder as bindResearchUiProcess
participant Proc as ResearchUiProcess
Main->>Launcher: launchResearchUiProcess(candidates, 0)
activate Launcher
Launcher->>Proc: spawn candidates[0]
Launcher->>Binder: bindResearchUiProcess(processHandle, pythonCommand, candidates, 0)
deactivate Launcher
note over Binder,Proc: During startup, process may exit or emit spawn error
alt process exit event
Proc-->>Binder: exit(code, signal)
Binder->>Binder: if researchServerProcess !== processHandle then return
Binder->>Binder: researchServerProcess = null
Binder->>Binder: researchServerOwned = false
alt nonzero code and workspaceState.status is starting and more candidates remain
Binder->>Main: appendLog(startup exit and retry message)
Binder->>Launcher: launchResearchUiProcess(candidates, candidateIndex + 1)
activate Launcher
Launcher->>Proc: spawn next candidates[candidateIndex + 1]
Launcher->>Binder: bindResearchUiProcess(newHandle, nextCommand, candidates, candidateIndex + 1)
deactivate Launcher
else no retry
Binder->>Main: clearResearchStartupTimer()
Binder->>Main: updateWorkspaceState(status stopped, error startup_error, source research_ui)
Binder->>Main: appendLog(startup exit message)
end
else spawn error event
Proc-->>Binder: error(errorObj)
Binder->>Binder: if researchServerProcess === processHandle then researchServerProcess = null and researchServerOwned = false
alt error.code in [EACCES, EPERM, ENOENT] and more candidates remain
Binder->>Main: appendLog(spawn error retry message)
Binder->>Launcher: launchResearchUiProcess(candidates, candidateIndex + 1)
activate Launcher
Launcher->>Proc: spawn next candidates[candidateIndex + 1]
Launcher->>Binder: bindResearchUiProcess(newHandle, nextCommand, candidates, candidateIndex + 1)
deactivate Launcher
else no retry
Binder->>Main: appendLog(spawn error without retry)
end
end
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="desktop/main.js" line_range="684-688" />
<code_context>
if (researchServerProcess !== processHandle) return;
researchServerProcess = null;
researchServerOwned = false;
+ const shouldRetry = code !== 0 && workspaceState.status === "starting" && candidateIndex < candidates.length - 1;
+ if (shouldRetry) {
+ const nextCommand = candidates[candidateIndex + 1];
+ appendLog(`[startup-exit] ${pythonCommand} exited (${code ?? "null"}${signal ? `, ${signal}` : ""}). Retrying with ${nextCommand}.`);
+ launchResearchUiProcess(candidates, candidateIndex + 1);
+ return;
+ }
</code_context>
<issue_to_address>
**issue (bug_risk):** Clear the research startup timer before retrying on non-zero exit to avoid stale timeouts interfering with the new process.
In the `exit` handler, the retry branch returns before `clearResearchStartupTimer()` is called, so any existing startup timer from the original launch can still fire during the retry and incorrectly push the workspace into an error state. Clear the startup timer before (or immediately after) starting the retry, and recreate it within `launchResearchUiProcess` as needed so each attempt uses its own timer and there are no leftover timeouts from previous attempts.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
desktop/main.js
Outdated
| const shouldRetry = code !== 0 && workspaceState.status === "starting" && candidateIndex < candidates.length - 1; | ||
| if (shouldRetry) { | ||
| const nextCommand = candidates[candidateIndex + 1]; | ||
| appendLog(`[startup-exit] ${pythonCommand} exited (${code ?? "null"}${signal ? `, ${signal}` : ""}). Retrying with ${nextCommand}.`); | ||
| launchResearchUiProcess(candidates, candidateIndex + 1); |
There was a problem hiding this comment.
issue (bug_risk): Clear the research startup timer before retrying on non-zero exit to avoid stale timeouts interfering with the new process.
In the exit handler, the retry branch returns before clearResearchStartupTimer() is called, so any existing startup timer from the original launch can still fire during the retry and incorrectly push the workspace into an error state. Clear the startup timer before (or immediately after) starting the retry, and recreate it within launchResearchUiProcess as needed so each attempt uses its own timer and there are no leftover timeouts from previous attempts.
7249533 to
19175d4
Compare
Summary
Adds a public landing page for QuantLab under
/landingwith a Hyperliquid-first product framing.Changes included:
landing/styles.csslanding/app.jsScope
This PR is landing-page only. It does not change QuantLab runtime, broker logic, or CLI behavior.
Validation
Validated with local review of the static HTML/CSS/JS and
git diff --check.Notes